using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace PopMail
{
    public class POP3Talker
    {
        //Class variables used to manage socket, states and responses

        private Socket m_socket;
        private IPEndPoint m_ep;
        private POP3Credentials m_pc;
        POP3StateObject m_pop3State;
        ManualResetEvent m_receiveDone;

        public POP3Talker(string host, int port, string userName, string password)
        {
            // Initialization of user credential object, socket for communication and state object
            m_socket = null;
            m_ep = null;
            m_pc = new POP3Credentials(host, port, userName, password);
            m_pop3State = null;
            m_receiveDone = null;
            m_socket = GetSocket();
        }

        public POP3Credentials Credentials
        {
            get { return m_pc; }
        }

        private Socket GetSocket()
        {
            Socket socket = null;

            try
            {
                IPHostEntry iphostEntry = null;

                iphostEntry = Dns.GetHostEntry(m_pc.Server);

                foreach (IPAddress address in iphostEntry.AddressList)
                {
                    IPEndPoint ipEndPoint = new IPEndPoint(address, m_pc.Port);
                    Socket tempSocket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                    tempSocket.Connect(ipEndPoint);

                    if (tempSocket.Connected)
                    {
                        socket = tempSocket;
                        m_ep = ipEndPoint;
                        break;
                    }
                    else
                    {
                        continue;
                    }
                }

                byte[] buffer = new byte[POP3Constants.M_BUFFER_SIZE];
                string line = null;

                int byteCount = socket.Receive(buffer, buffer.Length, 0);
                line = Encoding.ASCII.GetString(buffer, 0, byteCount);

                if (line.IndexOf("+OK") != -1)
                {
                    Console.WriteLine("Successfully connected");
                }

            }
            catch (Exception ex)
            {
                //connection exception
                Console.WriteLine(ex.ToString());
            }
            if (socket == null)
            {
                //error conencting to m_server
            }

            
            return socket;
        }

        public POP3ServerResponse SendCommand(string command, string args)
        {
            //A new state object

            m_pop3State = new POP3StateObject();
            m_pop3State.workSocket = m_socket;

            //A new event 
            m_receiveDone = new ManualResetEvent(false);
            
            //A new envelope to package return information
            POP3ServerResponse resp = null;

            bool status = true;
            string message = null;
            StringBuilder messageContent = new StringBuilder();
            
            try
            {
                if (m_socket == null)
                {
                    //connection closed
                }
                else
                {
                    //Send command
                    byte[] bytes = Encoding.ASCII.GetBytes(command + " " + args + "\r\n");
                    m_socket.Send(bytes);

                    if( command.Equals("user") || command.Equals("pass") 
                        || command.Equals("stat") || command.Equals("dele") || command.Equals("quit"))
                    {
                        m_pop3State.commandType = 1;
                    }

                    if (command.Equals("retr") || command.Equals("list"))
                    {
                        m_pop3State.commandType = 2;
                    }

                    //Get Response
                    m_socket.BeginReceive(m_pop3State.buffer, 0, POP3StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), m_pop3State);
                    m_receiveDone.WaitOne();
                    messageContent = m_pop3State.sb;
                    message = messageContent.ToString().Split(new char[] { '\r' })[0].ToString();

                    if (message.IndexOf("+OK") == -1)
                    {
                        status = false;
                        return resp;
                    }

                    resp = new POP3ServerResponse(status, message, messageContent);

                    m_pop3State.Dispose();
                    m_receiveDone = null;
                }
            }
            catch (Exception ex)
            {
                //throw new Pop3SendException(e.ToString());
                Console.WriteLine(ex.ToString());
            }

            return resp;

        }

        private void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the state object and the client socket from the asynchronous state object.

                POP3StateObject stateObj = (POP3StateObject)ar.AsyncState;
                Socket client = stateObj.workSocket;
                string delimiter = string.Empty;

                // Read data from the remote device.
                int bytesRead = client.EndReceive(ar);

                if (bytesRead > 0)
                {
                    // There might be more data, so store the data received so far.

                    stateObj.sb.Append(Encoding.UTF8.GetString(stateObj.buffer, 0, bytesRead));

                    if (stateObj.commandType == 1)
                        delimiter = "\r\n";
                    else
                        delimiter = "\r\n.\r\n";
 
                    if (!stateObj.sb.ToString().EndsWith(delimiter))
                    {
                        // Get the rest of the data.
                        client.BeginReceive(m_pop3State.buffer, 0, POP3StateObject.BufferSize, 0, 
                            new AsyncCallback(ReceiveCallback), m_pop3State);
                    }
                    else
                    {
                        m_receiveDone.Set();
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                m_receiveDone.Set();

                //throw new
                //    Pop3ReceiveException("RecieveCallback error" +
                //    e.ToString());
            }
        }

    }
}
